﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using ControleRepresentante.Logic;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories;
using ControleRepresentante.Logic.Statistics;
using WebSite.Controllers.ControllerExtensions;
using WebSite.ViewModels.DetailsViewModels;
using WebSite.ViewModels.EditViewModels;
using WebSite.ViewModels.IndexViewModels;

namespace WebSite.Controllers
{
    public class UsuarioController : CRUDControllerBase<IUserRepository, User, StatisticsBase>
    {
        private readonly IAreaOperacaoRegistry m_AreaOperacaoRegistry;

        public UsuarioController(IRepositoryProvider repositoryProvider, IAreaOperacaoRegistry areaOperacaoRegistry, ISessionConfiguration sessionConfiguration, IControllerExtensionProvider controllerExtensionProvider)
            : base(repositoryProvider, sessionConfiguration, controllerExtensionProvider)
        {
            m_AreaOperacaoRegistry = areaOperacaoRegistry;
        }

        protected override Area? Area { get { return ControleRepresentante.Logic.Models.Area.Usuarios; } }

        protected override DetailsViewModel GetDetailsViewModel(User model)
        {
            return new UsuarioDetailsViewModel(m_AreaOperacaoRegistry, Repository.GetRepresentante());
        }

        protected override void RegisterIndexColumns(IIndexColumnsProvider indexColumnsProvider)
        {
            base.RegisterIndexColumns(indexColumnsProvider);
            indexColumnsProvider.SetDefaultColumns(
                indexColumnsProvider.RegisterColumn("Username", UsuarioIndexColumns.Username, true),
                indexColumnsProvider.RegisterColumn("E-mail", UsuarioIndexColumns.Email),
                indexColumnsProvider.RegisterColumn("Ativo", UsuarioIndexColumns.Ativo));
        }

        public override ViewResult Details(Guid id)
        {
            ViewResult actionResult = base.Details(id);
            DetailsViewModel detailsViewModel = (DetailsViewModel) actionResult.Model;
            detailsViewModel.CanDelete &= Repository.User.Id != id;
            detailsViewModel.CanEdit &= Repository.User.Id != id;
            return actionResult;
        }

        public ActionResult Search()
        {
            return Index();
        }

        protected override bool CanDelete(User model, out string message)
        {
            if (Repository.User.Id == model.Id)
            {
                message = "Nao e possivel remover o usuario atual.";
                return false;
            }
            return base.CanDelete(model, out message);
        }

        public ActionResult CreateForFabrica(Guid id)
        {
            return CreateFor<Fabrica, IFabricaRepository, StatisticsBase>(id, (u, f) => u.SetFiltros(new [] {f}));
        }

        public ActionResult CreateForCliente(Guid id)
        {
            return CreateFor<Cliente, IClienteRepository, StatisticsBase>(id, (u, c) => u.SetFiltros(new [] {c}));
        }

        public ActionResult CreateForTransportadora(Guid id)
        {
            return CreateFor<Transportadora, ITransportadoraRepository, StatisticsBase>(id, (u, t) => u.SetFiltros(new [] {t}));
        }

        public ActionResult CreateForVendedor(Guid id)
        {
            return CreateFor<Vendedor, IVendedorRepository, StatisticsBase>(id, (u, v) => u.SetFiltros(new [] {v}));
        }

        private ActionResult CreateFor<TModel, TRepository, TStatistics>(Guid id, Action<User, TModel> setFiltro)
            where TModel : UsuariosHolder
            where TRepository : IRepository<TModel, TStatistics>
            where TStatistics : IStatistics
        {
            Model = CreateInstance();

            TRepository repository = RepositoryProvider.GetRepository<TRepository>();
            TModel model = repository.GetModelById(id);

            setFiltro(Model, model);

            Model.AddPermissao(new Permissao
            {
                Area = ControleRepresentante.Logic.Models.Area.Pedidos,
                Operacao = Operacao.View | Operacao.Print
            });

            return Create(false);
        }

        [HttpPost]
        public override ActionResult Delete(Guid id, FormCollection collection)
        {
            User user = Repository.User;
            if (user == null)
            {
                throw new RestrictedAreaException();
            }
            return user.Id == id ? Delete(id) : base.Delete(id, collection);
        }

        protected override ActionResult Edit(Guid? id = null)
        {
            User user = Repository.User;
            if (user == null)
            {
                throw new RestrictedAreaException();
            }
            if (user.Id == id)
            {
                return View(new UsuarioEditViewModel("Não é possível editar o usuário atual"));
            }
            return base.Edit(id);
        }

        protected override IIndexViewModel GetIndexViewModel(StatisticsBase statistics)
        {
            return new UsuarioIndexViewModel(Repository.User);
        }

        protected override EditViewModel GetEditViewModel(User model, bool isNew)
        {
            return new UsuarioEditViewModel(m_AreaOperacaoRegistry, Repository.GetRepresentante());
        }

        [HttpPost]
        public ActionResult Create(string username, string password, string email, bool ativo, string fabricaFiltro, string clienteFiltro, string vendedorFiltro, string transportadoraFiltro, FormCollection formCollection)
        {
            EnsureHasPermission(Operacao.Create);
            Dictionary<Guid, string> fabricas = ParseIds<IFabricaRepository, Fabrica, StatisticsBase>(fabricaFiltro, x => x.Nome);
            Dictionary<Guid, string> clientes = ParseIds<IClienteRepository, Cliente, StatisticsBase>(clienteFiltro, x => x.RazaoSocial);
            Dictionary<Guid, string> vendedores = ParseIds<IVendedorRepository, Vendedor, StatisticsBase>(vendedorFiltro, x => x.Nome);
            Dictionary<Guid, string> transportadoras = ParseIds<ITransportadoraRepository, Transportadora, StatisticsBase>(transportadoraFiltro, x => x.Nome);
            if (TryUpdateUser(username, password, email, ativo, fabricas, clientes, vendedores, transportadoras, formCollection))
            {
                try
                {
                    Repository.AddModel(Model);
                    User user = Model;
                    Model = null;
                    return RedirectToAction("Details", user.Id);
                }
                catch (RepositoryException<UserError> e)
                {
                    switch (e.Error)
                    {
                        case UserError.DuplicatedUsername:
                            ModelState.AddModelError("Username", "Username duplicado");
                            break;
                        default:
                            throw;
                    }
                }
            }
            return Create(false);
        }

        [HttpPost]
        public ActionResult Edit(string username, string password, string email, bool ativo, string fabricaFiltro, string clienteFiltro, string vendedorFiltro, string transportadoraFiltro, FormCollection formCollection)
        {
            EnsureHasPermission(Operacao.Update);
            Dictionary<Guid, string> fabricas = ParseIds<IFabricaRepository, Fabrica, StatisticsBase>(fabricaFiltro, x => x.Nome);
            Dictionary<Guid, string> clientes = ParseIds<IClienteRepository, Cliente, StatisticsBase>(clienteFiltro, x => x.RazaoSocial);
            Dictionary<Guid, string> vendedores = ParseIds<IVendedorRepository, Vendedor, StatisticsBase>(vendedorFiltro, x => x.Nome);
            Dictionary<Guid, string> transportadoras = ParseIds<ITransportadoraRepository, Transportadora, StatisticsBase>(transportadoraFiltro, x => x.Nome);
            if (TryUpdateUser(username, password, email, ativo, fabricas, clientes, vendedores, transportadoras, formCollection))
            {
                try
                {
                    Repository.UpdateModel(Model);
                    Guid userId = Model.Id;
                    Model = null;
                    return RedirectToAction("Details", userId);
                }
                catch (RepositoryException<UserError> e)
                {
                    switch (e.Error)
                    {
                        case UserError.DuplicatedUsername:
                            ModelState.AddModelError("Username", "Username duplicado");
                            break;
                        default:
                            throw;
                    }
                }
            }
            return Edit();
        }

        private bool TryUpdateUser(string username, string password, string email, bool ativo, Dictionary<Guid, string> fabricaFiltro, Dictionary<Guid, string> clienteFiltro, Dictionary<Guid, string> vendedorFiltro, Dictionary<Guid, string> transportadoraFiltro, FormCollection formCollection)
        {
            Model.Username = username;
            if (String.IsNullOrWhiteSpace(password))
            {
                Model.ClearPassword();
            }
            else
            {
                Model.SetPasswordFromRawText(password);
            }
            Model.Ativo = ativo;
            Model.Email = email;

            IEnumerable<UsuariosHolder> filtros = new UsuariosHolder[0];

            if (fabricaFiltro.Any())
            {
                IFabricaRepository fabricaRepository = RepositoryProvider.GetRepository<IFabricaRepository>();
                filtros = filtros.Union(fabricaRepository.GetModelsByIds(fabricaFiltro.Keys));
            }

            if (vendedorFiltro.Any())
            {
                IVendedorRepository vendedorRepository = RepositoryProvider.GetRepository<IVendedorRepository>();
                filtros = filtros.Union(vendedorRepository.GetModelsByIds(vendedorFiltro.Keys));
            }

            if (clienteFiltro.Any())
            {
                IClienteRepository clienteRepository = RepositoryProvider.GetRepository<IClienteRepository>();
                filtros = filtros.Union(clienteRepository.GetModelsByIds(clienteFiltro.Keys));
            }

            if (transportadoraFiltro.Any())
            {
                ITransportadoraRepository transportadoraRepository = RepositoryProvider.GetRepository<ITransportadoraRepository>();
                filtros = filtros.Union(transportadoraRepository.GetModelsByIds(transportadoraFiltro.Keys));
            }

            Model.SetFiltros(filtros);

            IEnumerable<Permissao> permissoes = GetPermissoesFromFormCollection(formCollection);
            foreach (Permissao permissao in permissoes)
            {
                Permissao existingPermissao = Model.Permissoes.FirstOrDefault(x => x.Area == permissao.Area);
                if (existingPermissao == null)
                {
                    Model.AddPermissao(permissao);
                }
                else
                {
                    existingPermissao.Operacao = permissao.Operacao;
                }
            }

            ModelState.Clear();
            if (String.IsNullOrEmpty(Model.Username) ||
                Model.Username.Trim() == String.Empty)
            {
                ModelState.AddModelError("Username", "*");
            }
            if (String.IsNullOrWhiteSpace(password) ||
                password.Trim() == String.Empty)
            {
                ModelState.AddModelError("Password", "*");
            }
            if (Repository.User.Id == Model.Id && !ativo)
            {
                ModelState.AddModelError("Ativo", "Nao e possivel desativar o usuario atual");
            }
            return ModelState.IsValid;
        }

        private IEnumerable<Permissao> GetPermissoesFromFormCollection(FormCollection formCollection)
        {
            List<Permissao> permissoes = new List<Permissao>();
            foreach (Area area in AreaOperacaoRegistry.GetAreas())
            {
                Permissao permissao = new Permissao {Area = area};
                foreach (Operacao operacao in m_AreaOperacaoRegistry.GetOperacoes(area))
                {
                    string stringValue = formCollection[area + "_" + operacao];
                    if (stringValue != null)
                    {
                        if (bool.Parse(stringValue.Split(',')[0]))
                        {
                            permissao.Operacao |= operacao;
                        }
                    }
                }
                permissoes.Add(permissao);
            }
            return permissoes;
        }
    }
}
